
/*
  Author:
  Lu Yiming <lu.yiming.lu@gmail.com>
 */

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <stddef.h>
#include <time.h>
#include <math.h>

#include "usage.h"
#include "udp_ipv4.h"
#include "data.h"

/* default UDP server port */
#define SERVER_PORT 8001


int make_socket (uint16_t port)
{
	int sock;
	struct sockaddr_in name;
     
	/* Create the socket. */
	sock = socket (PF_INET, SOCK_DGRAM, 0);
	if (sock < 0)
	{
		perror ("socket");
		exit (EXIT_FAILURE);
	}

	if (port) {
		name.sin_family = AF_INET;
		name.sin_port = htons (port);
		name.sin_addr.s_addr = htonl (INADDR_ANY);
		if (bind (sock, (struct sockaddr *) &name, sizeof (name)) < 0)
		{
			perror ("bind");
			exit (EXIT_FAILURE);
		}
	}
     
	return sock;
}

void udp_ipv4_server(int argc, char **argv)
{
	int sock;
	socklen_t size;             /* FIXME: size_t */ 
	int nbytes;
	void *buf_recv;
	uint16_t port = SERVER_PORT;
	struct sockaddr_in name;
	int old_argc = argc;
	int debug = 0;

	while (argc > 0) {
		if ((argc > 1) &&
		    (0 == strcmp(argv[0], "port"))) {
			port = atoi(argv[1]);
			if (!port) {
				fprintf(stderr, "port can't be 0\n");
				exit(EXIT_FAILURE);
			}
			argc -= 2;
			argv += 2;
		}

		if ((argc > 0) &&
		    (0 == strcmp(argv[0], "debug"))) {
			debug = 1;
			argc -= 1;
			argv += 1;
		}

		if (old_argc == argc) {
			usage();
			exit (EXIT_FAILURE);
		}
		old_argc = argc;
	}
     
	printf("udp ipv4 server started - port %d.\n", port);

	buf_recv = malloc(2000);
     
	/* Make the socket, then loop endlessly. */
	sock = make_socket(port);
	while (1)
	{
		size = sizeof(name);
		nbytes = recvfrom(sock, buf_recv, 2000, 0,
				   (struct sockaddr *)&name, &size);
		if (nbytes < 0)
		{
			perror ("recfrom (server)");
			exit (EXIT_FAILURE);
		}

		if (debug) {
			fprintf (stderr, "Server: got data, ID: %ld\n", *((unsigned long int *)buf_recv));
		}

		if (*(((unsigned int *)buf_recv + 2)) & (1 << 31)) {
			/* bit 31 means full-deplex */

			nbytes = sendto (sock, buf_recv, nbytes, 0,
					 (struct sockaddr *) &name, size);
			if (nbytes < 0)
			{
				perror ("sendto (server)");
				exit (EXIT_FAILURE);
			}
		}
	}
}

void init_sockaddr (struct sockaddr_in *name,
		    const char *hostname,
		    uint16_t port)
{
	struct hostent *hostinfo;
     
	name->sin_family = AF_INET;
	name->sin_port = htons(port);
	hostinfo = gethostbyname(hostname);
	if (hostinfo == NULL)
	{
		fprintf (stderr, "Unknown host %s.\n", hostname);
		exit (EXIT_FAILURE);
	}
	name->sin_addr = *(struct in_addr *) hostinfo->h_addr;
}

void udp_ipv4_client (int argc, char **argv)
{
	int sock;
	struct sockaddr_in name;
	size_t size;
	int nbytes;
	unsigned long int i;
	void *buf_send;
	void *buf_recv;
	time_t time_start;
	time_t time_end;
	unsigned long int packets = 128 * 1024;
	char duplex = 1;
	int old_argc = argc;
	uint16_t port = SERVER_PORT;
	char *server = "127.0.0.1";

	while (argc > 0) {
		if ((argc > 1) &&
		    (0 == strcmp(argv[0], "server"))) {
			server = argv[1];
			argc -= 2;
			argv += 2;
		}
		if ((argc > 1) &&
		    (0 == strcmp(argv[0], "port"))) {
			port = atoi(argv[1]);
			if (!port) {
				fprintf(stderr, "port can't be 0\n");
				exit(EXIT_FAILURE);
			}
			argc -= 2;
			argv += 2;
		}
		if ((argc > 1) &&
		    (0 == strcmp(argv[0], "packets"))) {
			packets = atoll(argv[1]);
			argc -= 2;
			argv += 2;
		}
		if ((argc > 0) &&
		    (0 == strcmp(argv[0], "full-duplex"))) {
			duplex = 2;
			argc -= 1;
			argv += 1;
		}
		if ((argc > 0) &&
		    (0 == strcmp(argv[0], "half-duplex"))) {
			duplex = 1;
			argc -= 1;
			argv += 1;
		}
		if (old_argc == argc) {
			usage();
			exit (EXIT_FAILURE);
		}
		old_argc = argc;
	}

	sock = make_socket(0);

	init_sockaddr(&name, server, port);

	buf_send = get_data();
	if (!buf_send) {
		perror ("get_data");
		exit (EXIT_FAILURE);
	}

	buf_recv = malloc(2000); /* we don't make UDP packet size more than 1400 */

	time(&time_start);

	for (i = 0; i < packets; i++) {

		/* buf[0-7] = ID, 0...n */
		*((unsigned long int *)buf_send) = i;
		/* buf[8-11] = message size */
		*(((unsigned int *)buf_send + 2)) = (1024 - 8 - 4) | 
		                                    ((1 == duplex) ? 0 : 1 << 31); /* bit 31 means full-deplex */

		size = sizeof(name);
		nbytes = sendto (sock, buf_send, 1024, 0,
		   (struct sockaddr *) & name, size);
		
		if (nbytes < 0)
		{
			perror ("sendto (client)");
			exit (EXIT_FAILURE);
		}

		if (2 == duplex) {
			nbytes = recvfrom (sock, buf_recv, 2000, 0, NULL, 0);
			if (nbytes < 0)
			{
				perror ("recfrom (client)");
				exit (EXIT_FAILURE);
			}
			if (1024 != nbytes)
			{
				perror ("recv size");
			}

			if (*((unsigned long int *)buf_recv) != i) {
				perror ("recv ID");
			}
			if ((*(((unsigned int *)buf_recv) + 2) & 0xffffff) != (1024 - 8 - 4)) {
				perror ("recv message size");
			}
		}
	}
     
	time(&time_end);

	fprintf (stderr, 
		 "Client: done. \n"
		 "Time elapsed (seconds): %'.2f\n"
		 "Packet (1KB) sent%s: %ld\n"
		 "Speed: %'.2f MBps\n"
		 , difftime(time_end, time_start)
		 , (1 == duplex) ? " (half-duplex)" : " and received (full-duplex)"
		 , packets
		 , 1.0 * packets * duplex / 1024 / ((0.0 == difftime(time_end, time_start)) ? 1.0 : difftime(time_end, time_start)));
     
	close (sock);
}
